/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning
  BTreeIndex idx;

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;
  int currIdx = 0;

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }
	//bool isEqual = true;
	
	SelCond* upperKey = NULL;
	SelCond* lowerKey = NULL;
	SelCond* equalKey = NULL;
	SelCond* notKey = NULL;
	SelCond* upperVal = NULL;
	SelCond* lowerVal = NULL;
	SelCond* equalVal = NULL;
	SelCond* notVal = NULL;
	
	int keyL = 0;
	int keyG = 0;
	int temp;
	
	for(unsigned i = 0; i < cond.size(); i++)
	{
		temp = atoi(cond[i].value);
		switch(cond[i].attr)
		{
			case 1:
					switch (cond[i].comp)
					{
						case SelCond::LE:
							temp++;
						case SelCond::LT:
							if(upperKey == NULL || atoi(upperKey->value) < temp)
							{
								upperKey = (SelCond*) &cond[i];
								if(upperKey->comp == SelCond::LE)
								{
									keyL = 1;
								}
								else
								{
									keyL = 0;
								}
							}
							break;
						case SelCond::GE:
							temp--;
						case SelCond::GT:
							if(lowerKey == NULL || atoi(lowerKey->value) > temp)
							{
								lowerKey = (SelCond*) &cond[i];
								if(lowerKey->comp == SelCond::GE)
								{
									keyG = 1;
								}
								else
								{
									keyG = 0;
								}
							}
							break;
						case SelCond::EQ:
							equalKey = (SelCond*) &cond[i];
							break;
						case SelCond::NE:
							//isEqual = false;
							notKey = (SelCond*) &cond[i];
							break;
					}
					break;
			case 2:
				switch (cond[i].comp)
				{
					case SelCond::LE:
						temp++;
					case SelCond::LT:
						if(upperVal == NULL || atoi(upperVal->value) < temp)
						{
							upperVal = (SelCond*) &cond[i];
						}
						break;
					case SelCond::GE:
						temp--;
					case SelCond::GT:
						if(lowerVal == NULL || atoi(lowerVal->value) > temp)
						{
							lowerVal = (SelCond*) &cond[i];
						}
						break;
					case SelCond::EQ:
						equalVal = (SelCond*) &cond[i];
						break;
					case SelCond::NE:
						//isEqual = false;
						notVal = (SelCond*) &cond[i];
						break;
				}
				break;
		}
	}

	if ((idx.open(table + ".idx", 'r')) == 0)
	{
		currIdx = 1;
		IndexCursor indexcursor;
		
		if(equalKey|| lowerKey || upperKey || notKey)
		{
			int findKey;
			int upperB;
			int lowerB;
			int keyNot;
			if(equalKey)
			{
				findKey = atoi(equalKey->value);
			}
			if(upperKey)
			{
				upperB = atoi(upperKey->value);
				findKey = upperB;
			}
			if(lowerKey)
			{
				lowerB = atoi(lowerKey->value);
				findKey = lowerB;
			}
			if(notKey)
			{
				keyNot = atoi(notKey->value);
			}
			if(! notKey)
			{
				if((rc = idx.locate(findKey, indexcursor)) != 0)
				{
					idx.close();
					return rc;
				}
			}
			
			//reading forward
			count = 0;
			while(idx.readForward(indexcursor,key,rid) == 0)
			{
				if(equalKey && key != findKey) continue;
				if(notKey && key == keyNot) continue;
				if(lowerKey)
				{
					if(keyG==1)
					{
						key < lowerB;
					}
					else
					{
						key <= lowerB;
					}
				continue;
				}
				if(upperKey)
				{
					if(keyL==1)
					{
						key > upperB;
					}
					else
					{
						key >= upperB;
					}
				break;
				}
				
				if((rc = rf.read(rid,key,value)) != 0)
				{
					fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
					idx.close();
					return rc;
				}
				
				//condition check
				if(upperVal != NULL && value >= upperVal->value) continue;
				if(lowerVal != NULL && value <= lowerVal->value) continue;
				if(equalVal != NULL && value != equalVal->value) continue;
				if(notVal && value <= notVal->value) continue;
				
				count++;
				
			switch (attr) 
			{
				case 1:
				  fprintf(stdout, "%d\n", key);
				  break;
				case 2: 
				  fprintf(stdout, "%s\n", value.c_str());
				  break;
				case 3:  // SELECT *
				  fprintf(stdout, "%d '%s'\n", key, value.c_str());
				  break;
			}
			
			if(equalKey && key == findKey) break;
			if(notKey && key != keyNot) break;
			}
		}
		
		if(equalKey || lowerKey || upperKey || notKey)
		{
			if(attr == 4)
			{
				fprintf(stdout,"%d\n", count);
			}
		}
		
		if(! (equalKey || lowerKey || upperKey))
		{
			idx.close();
			goto tablescan;
		}
		idx.close();
	}
	
	if(! currIdx)
	{
		tablescan:
		rid.pid = 0;
		rid.sid = 0;
		count = 0;
		
		while(rid< rf.endRid())
		{
			if((rc = rf.read(rid,key,value)) < 0)
			{
				 fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
		  		 goto exit_select;
			}
			
			//check tuple condition
			if(upperKey != NULL &&  (int) key >= atoi(upperKey->value)) goto next_tuple;
			if(lowerKey != NULL &&  (int) key <= atoi(lowerKey->value)) goto next_tuple;
			if(equalKey != NULL &&  (int) key != atoi(equalKey->value)) goto next_tuple;
			if(notKey && (int) key == atoi(notKey->value)) goto next_tuple;
			
			if(upperVal != NULL && value >= upperVal->value) goto next_tuple;
			if(lowerVal != NULL && value <= lowerVal->value) goto next_tuple;
			if(equalVal != NULL && value != equalVal->value) goto next_tuple;
			if(notVal && value == notVal->value) goto next_tuple;
			
			count++;
			
			switch (attr) 
			{
				case 1:  // SELECT key
				  fprintf(stdout, "%d\n", key);
				  break;
				case 2:  // SELECT value
				  fprintf(stdout, "%s\n", value.c_str());
				  break;
				case 3:  // SELECT *
				  fprintf(stdout, "%d '%s'\n", key, value.c_str());
				  break;
			}
			//next tuple
			
			next_tuple:
				++rid;
		}
		
		if(attr == 4)
		{
			fprintf(stdout, "%d\n", count);
		}
		rc = 0;
		
		exit_select:
			rf.close();
	
	}
	return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	
	RecordFile* myTable = new RecordFile(table + ".tbl", 'w');
	BTreeIndex idx;
	int key;
    string value;
    RecordId id;
	if(index)
	{
		idx.open(table + ".idx", 'w');
	}
	
	ifstream myfile;
	myfile.open(loadfile.c_str());
	string line;
	
	while(myfile.good() && getline(myfile,line, '\n'))
	{
		int parseError = parseLoadLine(line, key, value);
		if(parseError == 0)
		{
			int appendError = myTable->append(key,value,id);
			if(appendError == 0)
			{
				if(index)
				{
					if(idx.insert(key,id) != 0)
					{
						return RC_FILE_WRITE_FAILED;
					}
				}
			}
			else
			{
				return appendError;
			}
		}
		else
		{
			return parseError;
		}
	}
	
	idx.close();
	int tableCloseError = myTable->close();
	delete(myTable);
	if(tableCloseError != 0)
	{
		return tableCloseError;
	}
	
  // string line;
//   int key;
//   string value;
//   RecordId id;
//   string tableName = table + ".tbl";
//   RecordFile myTable;
//   myTable.open(tableName, 'w');
//   ifstream myfile (loadfile.c_str());
//   if (myfile.is_open())
//   {
// 	RecordFile myTable;
// 	myTable.open(tableName, 'w');
// 	while ( myfile.good() )
//     {
//       getline (myfile,line);
//       int parseError = parseLoadLine(line, key, value);
//       if(parseError != 0)
//       	return parseError;
//       int appendError = myTable.append(key,value, id);
//       if(appendError != 0)
//       	return appendError;
//     }
//     myfile.close();
//     if(myfile.is_open())
//     {
//     	RC_FILE_CLOSE_FAILED;
//     
//     }
//     int tableCloseError = myTable.close();
//     if(tableCloseError != 0)
//     {
//     	return tableCloseError;
//     }
//   }
//   else 
//   {
//   		//cerr << "Unable to open file"; 
//   		return RC_FILE_OPEN_FAILED;
//   }
  return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
